/*
 * ********************************************************************************
 * Project:        GAMES, 7th Framework Programme, co-funded by European Commission
 * Product:        ESMI Prototype
 * File:           AssociationRuleMiner.java (UTF-8)
 * Reviewd by:     Alessandro Rossi <alessandro.rossi@eng.it>
 * Reviewd on:     31-dic-2011 22.51.24
 * 
 * (C) Copyright 2010-2012 Engineering Ingegneria Informatica S.p.A.
 * 
 * This file is part of the Energy Sensing & Monitoring Infrastructure of GAMES.
 * ********************************************************************************
 * 
 * The terms of the license are under discussion and will be defined.
 * Until then, the distribution of this file is restricted to the members of the GAMES Consortium.
 */
/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package eu.games.esmi.ieat.indicatorrelations.data;

import java.util.Hashtable;
import java.util.Enumeration;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;

import weka.core.converters.ArffLoader;
import weka.core.FastVector;
import weka.associations.Apriori;
import weka.core.Instances;
import weka.associations.*;
import weka.core.Utils;
import weka.core.stemmers.Stemmer;

/**
 *
 * @author darnone
 */
public class AssociationRuleMiner {
    
    private boolean firstLoad;
    private int numberOfInstances;
    private Hashtable<String,String[]> dataset = new Hashtable<String,String[]>();
    private String msg;
    private ArffLoader loader;
    private FastVector[] alltheRules;
    private String associationRulesString;
    private Instances m_instances;
    
    public AssociationRuleMiner(){
        firstLoad = true;
        loader = new ArffLoader();
    }
    
    public boolean load(String gpikpi_id, String[] values){
       
        if ((!firstLoad)){
            if (numberOfInstances!=values.length){
                msg = "number of instances must be always the same";
                return false;
            }
            if (dataset.containsKey(gpikpi_id)){
                msg = "two different GPI/KPI cannot have the same name";
                dataset.put(gpikpi_id, values);            
                return false;
            }
            dataset.put(gpikpi_id, values);
            return true;
        }
        numberOfInstances = values.length;
        dataset.put(gpikpi_id, values);
        firstLoad = false;
        return true;
    };
    
    private void createARFFFile(){
        String type = "green, yellow, red";
        try{
            File instancesFile = new File("tempARFFFile.arff");
            FileWriter fstream = new FileWriter(instancesFile);
            BufferedWriter out = new BufferedWriter(fstream);
            int numberOfAttributes = dataset.size();
            
            Enumeration<String> attributeNames = dataset.keys();            
            String[][] attributeValuesMatrix = new String[numberOfAttributes][numberOfInstances];
            out.write("@relation 'temprelation'\n");
            int counter = 0;
            String currentAttributeName = "";
            while (attributeNames.hasMoreElements()){
                currentAttributeName = attributeNames.nextElement();
                out.write("@attribute '"+currentAttributeName+"' {"+type+"}\n");                  
                attributeValuesMatrix[counter++] = dataset.get(currentAttributeName);
            }
            out.write("@data\n");
 
            String currentInstance = "";
            System.out.println("numberOfInstances = "+numberOfInstances);
            for(int i=0; i < numberOfInstances; i++){                  
                for (int j=0; j < numberOfAttributes; j++){        
                    currentInstance += attributeValuesMatrix[j][i]+",";
                }
                currentInstance = currentInstance.substring(0, currentInstance.lastIndexOf(','))+"\n";
                //System.out.println(currentInstance);
                out.write(currentInstance);  
                currentInstance ="";
            }
            out.close();
            InputStream istrm = new FileInputStream (instancesFile);
            loader.setSource(istrm);
            
        }catch (Exception e){
            System.err.println("Error: " + e.getMessage());
        };

    }
    
    public void mineAssociations(String[] options){
        
        createARFFFile();
        Apriori assRulMiner = new Apriori();
        
        try{
            
            assRulMiner.setOptions(options);
            assRulMiner.buildAssociations(loader.getDataSet());
            alltheRules = assRulMiner.getAllTheRules();
            associationRulesString =  assRulMiner.toString();    
            m_instances = assRulMiner.getInstancesNoClass();
        }catch(Exception e){        
            System.err.println("Error: " + e.getStackTrace());
        }
        
    };
    
    public FastVector[] getMinedAssociationRules(){
        return alltheRules;
    }
    
    public FastVector[] getAssociationRules(String[] ant_gpi_kpi_ids, String[] con_gpi_kpi_ids, double minsup, double minconf){
        //check
        return alltheRules;
    }
    
    public String getAssociationRulesString(){
        return associationRulesString;
    }
    
    public Instances getInstances(){
        return m_instances;
    }
    
    public GmsAssRule[] getAssociationRulesArray(){
       
        //create rules array        
        GmsAssRule[] assRules = new GmsAssRule[alltheRules[0].size()];
        
        for (int k = 0; k < alltheRules[0].size(); k++){            
     
            assRules[k] = new GmsAssRule(
                    (AprioriItemSet)alltheRules[0].elementAt(k), 
                    (AprioriItemSet)alltheRules[1].elementAt(k),
                    ((AprioriItemSet)alltheRules[1].elementAt(k)).support()/(double)numberOfInstances,
                    ((Double)alltheRules[2].elementAt(k)).doubleValue());
        }
        
        return assRules;
    }
    
    public static String demo(){
        return demo(new String[] {});
    }
    
    public static String demo(String args[]){
        //test code
        //example of instances
        String[] gpi_kpi_1 ={"red", "yellow","red", "green" , "red","red", "green","green"};
        String[] gpi_kpi_2 ={"red", "green","red", "yellow", "green", "yellow", "green" , "red"};
        String[] gpi_kpi_3 ={"green", "green","green", "yellow", "green" , "red","red", "yellow"};
        String[] gpi_kpi_4 ={"yellow", "green","green", "red", "green" , "red","red", "green"};
        
        //instantiate the AssociationRuleMiner
        AssociationRuleMiner testMiner = new AssociationRuleMiner();
        
        //load the instances
        testMiner.load("gpi_kpi_1", gpi_kpi_1);
        testMiner.load("gpi_kpi_2", gpi_kpi_2);
        testMiner.load("gpi_kpi_3", gpi_kpi_3);
        testMiner.load("gpi_kpi_4", gpi_kpi_4);

        //define options  

        // -N <required number of rules output>;
        // The required number of rules. (default = 10)</pre>
 
        // -T <0=confidence | 1=lift | 2=leverage | 3=Conviction>;
        //The metric type by which to rank rules. (default = confidence)</pre>
 
        // -C <minimum metric score of a rule>;
        // The minimum confidence of a rule. (default = 0.9)</pre>

        // -D <delta for minimum support>;
        // The delta by which the minimum support is decreased in
        // each iteration. (default = 0.05)</pre>

        // -U <upper bound for minimum support>;
        // Upper bound for minimum support. (default = 1.0)</pre>

        // -M <lower bound for minimum support>;
        // The lower bound for the minimum support. (default = 0.1)</pre>

        // -S <significance level>;
        // If used, rules are tested for significance at
        // the given level. Slower. (default = no significance testing)</pre>

        // -I
        //If set the itemsets found are also output. (default = no)</pre>
        
        // -R
        // Remove columns that contain all missing values (default = no)</pre>
 
        // -V
        // Report progress iteratively. (default = no)</pre>

        // -A
        // If set class association rules are mined. (default = no)</pre>

        // -c <the class index>;
        //The class index. (default = last)</pre>
        
        String maximumNumberOfRules = "1000";
        String minumumConfidence = "0.30";
        String minimumSupport = "0.15";
        
        String[] options = {
            "-N",maximumNumberOfRules,
            "-C",minumumConfidence,
            "-M",minimumSupport 
        };
        
        //set options and mine associations
        testMiner.mineAssociations(options);
        
        //get all the extracted rules as a big string to show, for instance, into a text box
        String associationRulesString = testMiner.getAssociationRulesString();

        //output the association rules as string
        System.out.println("Miner output:\n"+associationRulesString);
        
        //output rule by rule
        GmsAssRule[] rules = testMiner.getAssociationRulesArray();
        
        String result="";
        for (int i = 0; i < rules.length; i++) {
            result += (i+1) + ". " +
                    rules[i].getAntecedent().toString(testMiner.getInstances()) + " ==> " +
                    rules[i].getConsequent().toString(testMiner.getInstances()) + " sup:(" +
                    Utils.doubleToString(rules[i].getSupport(),2) + ") conf:(" +
                    Utils.doubleToString(rules[i].getConfidence(),2)+ " )/n"; 
        }
        return result;
    }
    
    public class GmsAssRule {
        private AprioriItemSet m_antecedent; 
        private AprioriItemSet m_consequent; 
        private double m_support; 
        private double m_confidence; 
        public GmsAssRule(AprioriItemSet antecedent, AprioriItemSet consequent, double support, double confidence){
            m_antecedent = antecedent;
            m_consequent = consequent;
            m_support = support;
            m_confidence = confidence;
        }
        public AprioriItemSet getAntecedent(){
            return m_antecedent;
        }
        public AprioriItemSet getConsequent(){
            return m_consequent;
        }
        public double getSupport(){
            return m_support;
        }
        public double getConfidence(){
            return m_confidence;
        }
    }
}